/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	/// <remarks>
	/// All public methods in this class are thread-safe; however, public properties are not thread-safe.
	/// </remarks>
	public sealed class DirectoryCopier
	{
		#region Public Properties
		/// <summary>
		/// Gets or sets whether the ACLs for the source directories are copied to the target directories.  The default value is <b>false</b>.
		/// </summary>
		public bool IncludeDirectorySecurity { get; set; }

		/// <summary>
		/// Gets or sets whether files are copied.  The default value is <b>true</b>.
		/// </summary>
		public bool CopyFiles { get; set; }

		/// <summary>
		/// Gets or sets whether directories that do not contain any files are copied.  The default value is <b>true</b>.
		/// </summary>
		public bool CopyEmptyDirectories { get; set; }

		/// <summary>
		/// Gets or sets whether all sub directories and their contents are copied, recursively.  The default value is <b>true</b>.
		/// </summary>
		/// <value><b>true</b> specifies that all sub directories and files are copied, recursively; otherwise, <b>false</b> specifies that 
		/// only the source directory and its files are copied.</value>
		public bool DeepCopy { get; set; }

		public bool DeleteSource { get; set; }

		public string FilePattern { get; set; }

		public bool SourceDirectoryRequired { get; set; }

		public bool FlattenHierarchy { get; set; }
		#endregion

		#region Private / Protected
		private readonly object sync = new object();
		private volatile bool stop;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="DirectoryCopier" /> class.
		/// </summary>
		public DirectoryCopier()
		{
			CopyFiles = true;
			CopyEmptyDirectories = true;
			DeepCopy = true;
			SourceDirectoryRequired = true;
		}
		#endregion

		#region Methods
		/// <summary>
		/// Performs a copy of the specified <paramref name="source" /> directory to the specified <paramref name="target" /> directory, 
		/// creating the <paramref name="target" /> directory if it doesn't already exist.
		/// </summary>
		public void CopyDirectory(string source, string target)
		{
			lock (sync)
			{
				stop = false;

				DirectoryInfo sourceDir = new DirectoryInfo(source);
				DirectoryInfo targetDir = new DirectoryInfo(target);

				if (DeepCopy)
					CopyDirectoryRecursive(SourceDirectoryRequired, sourceDir, targetDir);
				else
					CopyDirectory(SourceDirectoryRequired, sourceDir, targetDir);
			}
		}

		[System.Diagnostics.DebuggerHidden]
		private void CopyDirectoryRecursive(bool sourceRequired, DirectoryInfo source, DirectoryInfo target)
		{
			if (stop)
				return;

			DirectoryInfo[] subDirectories;

			try
			{
				subDirectories = source.GetDirectories();
			}
			catch (DirectoryNotFoundException ex)
			{
				if (sourceRequired)
					throw new DirectoryNotFoundException(Errors.CopyDirSourceDoesntExist + source.FullName, ex);
				else
					return;
			}

			Debug.Indent();

			try
			{
				// Delete most-contained directories first in case deleteSource is true.  This way directories and files
				// can be moved immediately (copied, then deleted): 
				foreach (DirectoryInfo subDirectory in subDirectories)
					CopyDirectoryRecursive(false, subDirectory, (FlattenHierarchy) ? target : new DirectoryInfo(Path.Combine(target.FullName, subDirectory.Name)));

				if (stop)
					return;

				CopyDirectory(sourceRequired, source, target);

				if (stop)
					return;
			}
			finally
			{
				Debug.Unindent();
			}
		}

		[System.Diagnostics.DebuggerHidden]
		private void CopyDirectory(bool sourceRequired, DirectoryInfo source, DirectoryInfo target)
		{
			FileInfo[] sourceFiles;

			try
			{
				sourceFiles = (string.IsNullOrEmpty(FilePattern)) ? source.GetFiles() : source.GetFiles(FilePattern, SearchOption.TopDirectoryOnly);
			}
			catch (DirectoryNotFoundException ex)
			{
				if (sourceRequired)
					throw new DirectoryNotFoundException(Errors.CopyDirSourceDoesntExist + source.FullName, ex);
				else
					return;
			}

			if (!target.Exists && (CopyEmptyDirectories || sourceFiles.Length > 0))
			{
				Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "Creating directory \"{0}\"", target.FullName));

				if (IncludeDirectorySecurity)
					target.Create(source.GetAccessControl());
				else
					target.Create();
			}

			if (CopyFiles)
			{
				foreach (FileInfo file in sourceFiles)
				{
					if (stop)
						return;

					Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0} file \"{1}\" to \"{2}\"",
						(DeleteSource) ? "Moving" : "Copying", file.FullName, target.FullName));

					string targetFile = Path.Combine(target.FullName, file.Name);

					if (!file.CopyTo(targetFile, true).Exists)
						throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, Errors.CopyFileFailed, targetFile));

					if (DeleteSource)
						file.Delete();
				}
			}

			if (DeleteSource)
				source.Delete(true);
		}

		/// <summary>
		/// Stops a copy operation that is already in progress on another thread, started with a call to the <see cref="CopyDirectory" /> method.
		/// </summary>
		public void Stop()
		{
			stop = true;
		}
		#endregion
	}
}
